#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import json
import re
import time
import random
import os
from urllib.parse import urlparse, unquote

class WordPressPublisher:
    """
    优化后的WordPress文章发布器
    基于curl.txt中的实际请求流程进行优化
    """
    
    def __init__(self, config_file='configs/config.json'):
        """
        初始化WordPress发布器
        :param config_file: 配置文件路径
        """
        config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), config_file)
        print(f"使用配置文件: {config_file}")
        self.config = self._load_config(config_file)
        self.session = requests.Session()
        
        # 设置API URL
        self.site_url = self.config.get('site_url', 'https://baikekuai.com')
        self.api_url = f"{self.site_url}/wp-json/wp/v2"
        self.admin_url = f"{self.site_url}/wp-admin"
        self.ajax_url = f"{self.site_url}/wp-admin/admin-ajax.php"
        
        # 添加重试配置
        self.max_retries = self.config.get('max_retries', 3)
        self.retry_delay = self.config.get('retry_delay', 2)  # 秒
        self.request_timeout = self.config.get('request_timeout', 30)  # 秒
        self.request_delay = self.config.get('request_delay', 2)
        self.last_request_time = 0
        
        # 设置基本请求头
        headers = {
            'Accept': 'application/json, */*;q=0.1',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
            'Cache-Control': 'no-cache',
            'Pragma': 'no-cache',
            'Origin': self.site_url,
            'Priority': 'u=1, i',
            'Sec-Ch-Ua': '"Microsoft Edge";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
            'Sec-Ch-Ua-Mobile': '?0',
            'Sec-Ch-Ua-Platform': '"Windows"',
            'Sec-Fetch-Dest': 'empty',
            'Sec-Fetch-Mode': 'cors',
            'Sec-Fetch-Site': 'same-origin'
        }
        
        if self.config.get('user_agent'):
            headers['User-Agent'] = self.config['user_agent']
        else:
            headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0'
        
        self.session.headers.update(headers)
        
        # 设置cookies
        if 'cookie_string' in self.config:
            cookies = self.parse_cookies_from_string(self.config['cookie_string'])
            self.session.cookies.update(cookies)
    
    def _load_config(self, config_file):
        """
        加载配置文件
        :param config_file: 配置文件路径
        :return: 配置字典
        """
        try:
            print(f"尝试加载配置文件: {config_file}")
            with open(config_file, 'r', encoding='utf-8') as f:
                config = json.load(f)
            return config
        except FileNotFoundError:
            print(f"配置文件 {config_file} 不存在")
            return {}
        except json.JSONDecodeError:
            print(f"配置文件 {config_file} 格式错误")
            return {}
        except Exception as e:
            print(f"加载配置文件时出错: {e}")
            return {}
    
    def _wait_between_requests(self):
        """在请求之间添加延迟，避免请求过于频繁"""
        current_time = time.time()
        time_since_last_request = current_time - self.last_request_time
        
        if time_since_last_request < self.request_delay:
            sleep_time = self.request_delay - time_since_last_request + random.uniform(0, 0.5)
            time.sleep(sleep_time)
        
        self.last_request_time = time.time()
    
    def parse_cookies_from_string(self, cookie_string):
        """
        从cookie字符串解析为字典
        :param cookie_string: cookie字符串，如 'name1=value1; name2=value2'
        :return: cookie字典
        """
        cookies = {}
        if cookie_string:
            for item in cookie_string.split(';'):
                if '=' in item:
                    key, value = item.strip().split('=', 1)
                    # 解码URL编码的值
                    try:
                        value = unquote(value)
                    except:
                        pass
                    cookies[key] = value
        return cookies
    
    def extract_nonce_from_page(self, post_id=None):
        """
        从WordPress编辑页面提取nonce或获取动态nonce
        :param post_id: 文章ID，如果为None则创建新文章
        :return: nonce字符串
        """
        try:
            # 首先尝试获取动态nonce
            nonce_url = f"{self.site_url}/wp-admin/admin-ajax.php?action=rest-nonce"
            response = self.session.get(nonce_url, timeout=self.request_timeout)
            
            if response.status_code == 200:
                nonce = response.text.strip()
                print(f"获取到动态Nonce: {nonce}")
                return nonce
            else:
                print(f"无法获取动态Nonce (状态码: {response.status_code})")
        except Exception as e:
            print(f"获取动态Nonce出错: {str(e)}")
        
        # 如果获取动态nonce失败，使用已知的有效nonce值作为备用
        return "953e889243"
    
    def extract_meta_box_nonces(self, post_id=None):
        """
        从WordPress编辑页面提取meta box相关的nonce
        :param post_id: 文章ID，如果为None则创建新文章
        :return: 包含各种nonce的字典
        """
        try:
            self._wait_between_requests()
            
            if post_id:
                url = f"{self.admin_url}/post.php?post={post_id}&action=edit"
            else:
                url = f"{self.admin_url}/post-new.php"
            
            response = self.session.get(url)
            response.raise_for_status()
            
            nonces = {}
            
            # 提取各种nonce
            nonce_patterns = {
                'wpnonce': r'_wpnonce["\']?\s*[:=]\s*["\']([^"\']+)["\']',
                'meta_box_order_nonce': r'meta-box-order-nonce["\']?\s*[:=]\s*["\']([^"\']+)["\']',
                'closedpostboxesnonce': r'closedpostboxesnonce["\']?\s*[:=]\s*["\']([^"\']+)["\']',
                'samplepermalinknonce': r'samplepermalinknonce["\']?\s*[:=]\s*["\']([^"\']+)["\']',
                'wp_http_referer': r'_wp_http_referer["\']?\s*[:=]\s*["\']([^"\']+)["\']',
            }
            
            for name, pattern in nonce_patterns.items():
                match = re.search(pattern, response.text)
                if match:
                    nonces[name] = match.group(1)
            
            return nonces
        except Exception as e:
            print(f"提取meta box nonces失败: {e}")
            return {}
    
    def create_article(self, title, content, status='draft', category_id=1, featured_media_id=None, use_nonce=True):
        """
        创建新文章
        :param title: 文章标题
        :param content: 文章内容（HTML格式）
        :param status: 文章状态 ('draft', 'publish', 'private', etc.)
        :param category_id: 分类ID
        :param featured_media_id: 特色图片ID
        :param use_nonce: 是否使用nonce验证
        :return: 创建结果
        """
        try:
            self._wait_between_requests()
            
            # 准备文章数据
            post_data = {
                'title': title,
                'content': content,
                'status': status,
                'categories': [category_id] if category_id else []
            }
            
            # 如果有特色图片ID，添加到数据中
            if featured_media_id:
                post_data['featured_media'] = featured_media_id
            
            # 如果需要使用nonce，尝试获取
            nonce = None
            if use_nonce:
                nonce = self.extract_nonce_from_page()
                if nonce:
                    # 将nonce添加到请求头，而不是请求体
                    headers = {'X-WP-Nonce': nonce}
                    # 创建文章
                    response = self.session.post(
                        f"{self.api_url}/posts",
                        json=post_data,
                        headers=headers,
                        timeout=self.request_timeout
                    )
                else:
                    # 创建文章
                    response = self.session.post(
                        f"{self.api_url}/posts",
                        json=post_data,
                        timeout=self.request_timeout
                    )
            else:
                # 创建文章
                response = self.session.post(
                    f"{self.api_url}/posts",
                    json=post_data,
                    timeout=self.request_timeout
                )
            
            if response.status_code == 201:
                post_data = response.json()
                print(f"文章创建成功，ID: {post_data['id']}")
                return {'success': True, 'post_id': post_data['id'], 'post_data': post_data}
            else:
                print(f"文章创建失败，状态码: {response.status_code}")
                
                # 尝试解析JSON响应并解码Unicode字符
                try:
                    error_data = response.json()
                    if 'message' in error_data:
                        # 解码Unicode编码的中文字符
                        message = error_data['message']
                        if '\\u' in message:
                            message = message.encode('utf-8').decode('unicode_escape')
                        print(f"错误信息: {message}")
                    else:
                        print(f"响应内容: {response.text}")
                except:
                    print(f"响应内容: {response.text}")
                
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            print(f"创建文章时出错: {e}")
            return {'success': False, 'error': str(e)}
    
    def update_article(self, post_id, title=None, content=None, status=None, category_id=None, featured_media_id=None, use_nonce=True):
        """
        更新现有文章
        :param post_id: 文章ID
        :param title: 新标题
        :param content: 新内容
        :param status: 新状态
        :param category_id: 新分类ID
        :param featured_media_id: 特色图片ID
        :param use_nonce: 是否使用nonce验证
        :return: 更新结果
        """
        try:
            self._wait_between_requests()
            
            # 准备更新数据
            post_data = {}
            
            if title is not None:
                post_data['title'] = title
            if content is not None:
                post_data['content'] = content
            if status is not None:
                post_data['status'] = status
            if category_id is not None:
                post_data['categories'] = [category_id]
            if featured_media_id is not None:
                post_data['featured_media'] = featured_media_id
            
            # 如果需要使用nonce，尝试获取
            nonce = None
            if use_nonce:
                nonce = self.extract_nonce_from_page(post_id)
                if nonce:
                    # 将nonce添加到请求头，而不是请求体
                    headers = {'X-WP-Nonce': nonce}
                    # 更新文章
                    response = self.session.post(
                        f"{self.api_url}/posts/{post_id}",
                        json=post_data,
                        headers=headers,
                        timeout=self.request_timeout
                    )
                else:
                    # 更新文章
                    response = self.session.post(
                        f"{self.api_url}/posts/{post_id}",
                        json=post_data,
                        timeout=self.request_timeout
                    )
            else:
                # 更新文章
                response = self.session.post(
                    f"{self.api_url}/posts/{post_id}",
                    json=post_data,
                    timeout=self.request_timeout
                )
            
            if response.status_code == 200:
                post_data = response.json()
                print(f"文章更新成功，ID: {post_data['id']}")
                return {'success': True, 'post_id': post_data['id'], 'post_data': post_data}
            else:
                print(f"文章更新失败，状态码: {response.status_code}")
                
                # 尝试解析JSON响应并解码Unicode字符
                try:
                    error_data = response.json()
                    if 'message' in error_data:
                        # 解码Unicode编码的中文字符
                        message = error_data['message']
                        if '\\u' in message:
                            message = message.encode('utf-8').decode('unicode_escape')
                        print(f"错误信息: {message}")
                    else:
                        print(f"响应内容: {response.text}")
                except:
                    print(f"响应内容: {response.text}")
                
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            print(f"更新文章时出错: {e}")
            return {'success': False, 'error': str(e)}
    
    def set_featured_image(self, post_id, image_path, filename=None, use_nonce=True):
        """
        设置文章的特色图片
        :param post_id: 文章ID
        :param image_path: 图片本地路径
        :param filename: 自定义文件名
        :param use_nonce: 是否使用nonce验证
        :return: 设置结果
        """
        try:
            # 先上传图片
            upload_result = self.upload_media(image_path, filename, use_nonce)
            if not upload_result['success']:
                return upload_result
            
            # 获取上传后的媒体ID
            media_id = upload_result['media_id']
            
            # 将图片设置为特色图片
            self._wait_between_requests()
            
            headers = {}
            
            # 如果使用nonce，尝试获取
            if use_nonce:
                nonce = self.extract_nonce_from_page(post_id)
                if nonce:
                    headers['X-WP-Nonce'] = nonce
                else:
                    print("警告: 无法获取nonce，尝试继续...")
            
            # 更新文章，设置特色图片
            post_data = {
                'id': post_id,
                'featured_media': media_id
            }
            
            response = self.session.post(
                f"{self.api_url}/posts/{post_id}?_locale=user",
                headers=headers,
                data=json.dumps(post_data)
            )
            
            if response.status_code == 200:
                result = response.json()
                print(f"特色图片设置成功，文章ID: {result.get('id')}, 媒体ID: {media_id}")
                return {'success': True, 'post_id': result.get('id'), 'media_id': media_id, 'data': result}
            else:
                print(f"特色图片设置失败，状态码: {response.status_code}")
                print(f"响应内容: {response.text}")
                
                # 如果使用nonce失败，尝试不使用nonce
                if use_nonce and response.status_code == 401:
                    print("尝试不使用nonce重新设置...")
                    return self.set_featured_image(post_id, image_path, use_nonce=False)
                
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            print(f"设置特色图片时出错: {e}")
            return {'success': False, 'error': str(e)}
    
    def get_categories(self):
        """
        获取所有分类
        :return: 分类列表
        """
        try:
            self._wait_between_requests()
            
            response = self.session.get(
                f"{self.api_url}/categories",
                timeout=self.request_timeout
            )
            
            if response.status_code == 200:
                categories = response.json()
                print(f"获取分类成功，共 {len(categories)} 个分类")
                return {'success': True, 'categories': categories}
            else:
                print(f"获取分类失败，状态码: {response.status_code}")
                print(f"响应内容: {response.text}")
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            print(f"获取分类时出错: {e}")
            return {'success': False, 'error': str(e)}
    
    def get_tags(self):
        """
        获取所有标签
        :return: 标签列表
        """
        try:
            self._wait_between_requests()
            
            response = self.session.get(
                f"{self.api_url}/tags",
                timeout=self.request_timeout
            )
            
            if response.status_code == 200:
                tags = response.json()
                print(f"获取标签成功，共 {len(tags)} 个标签")
                return {'success': True, 'tags': tags}
            else:
                print(f"获取标签失败，状态码: {response.status_code}")
                print(f"响应内容: {response.text}")
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            print(f"获取标签时出错: {e}")
            return {'success': False, 'error': str(e)}
    
    def create_tag(self, name):
        """
        创建新标签
        :param name: 标签名称
        :return: 创建结果
        """
        try:
            self._wait_between_requests()
            
            # 准备标签数据
            tag_data = {
                'name': name
            }
            
            # 创建标签
            response = self.session.post(
                f"{self.api_url}/tags",
                json=tag_data,
                timeout=self.request_timeout
            )
            
            if response.status_code == 201:
                tag_data = response.json()
                print(f"标签创建成功，ID: {tag_data['id']}")
                return {'success': True, 'tag_id': tag_data['id'], 'tag_data': tag_data}
            else:
                print(f"标签创建失败，状态码: {response.status_code}")
                print(f"响应内容: {response.text}")
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            print(f"创建标签时出错: {e}")
            return {'success': False, 'error': str(e)}
    
    def publish_with_featured_image(self, title, content, image_path, status='draft', category_id=None, tags=None, filename=None):
        """
        创建文章并设置特色图片（一体化方法）
        :param title: 文章标题
        :param content: 文章内容
        :param image_path: 图片路径
        :param status: 文章状态
        :param category_id: 分类ID
        :param tags: 标签ID列表
        :param filename: 自定义文件名
        :return: 发布结果
        """
        try:
            # 先上传图片
            upload_result = self.upload_media(image_path, filename)
            if not upload_result['success']:
                return upload_result
            
            # 获取上传后的媒体ID
            media_id = upload_result['media_id']
            
            # 创建文章，并设置特色图片
            create_result = self.create_article(
                title=title,
                content=content,
                status=status,
                category_id=category_id,
                featured_media_id=media_id,
                use_nonce=True
            )
            
            if not create_result['success']:
                return {'success': False, 'error': f"文章创建失败: {create_result['error']}"}
            
            post_id = create_result['post_id']
            
            # 如果有标签，添加标签
            if tags:
                self._wait_between_requests()
                
                # 获取动态nonce
                nonce = self.extract_nonce_from_page(post_id)
                headers = {}
                if nonce:
                    headers['X-WP-Nonce'] = nonce
                else:
                    print("警告: 无法获取nonce，尝试继续...")
                
                # 更新文章，添加标签
                post_data = {
                    'id': post_id,
                    'tags': tags
                }
                
                response = self.session.post(
                    f"{self.api_url}/posts/{post_id}?_locale=user",
                    headers=headers,
                    data=json.dumps(post_data)
                )
                
                if response.status_code != 200:
                    print(f"添加标签失败，状态码: {response.status_code}")
                    print(f"响应内容: {response.text}")
            
            print(f"文章发布成功，ID: {post_id}, 特色图片ID: {media_id}")
            return {
                'success': True,
                'post_id': post_id,
                'media_id': media_id,
                'data': create_result.get('data')
            }
            
        except Exception as e:
            print(f"发布文章时出错: {e}")
            return {'success': False, 'error': str(e)}
    
    def get_api_info(self):
        """
        获取WordPress API信息
        :return: API信息字典
        """
        api_info = {
            "base_url": self.api_url,
            "admin_url": self.admin_url,
            "ajax_url": self.ajax_url,
            "authentication": "需要登录验证",
            "required_headers": {
                "Content-Type": "application/json",
                "X-WP-Nonce": "从页面提取的nonce值（某些情况下必需）",
                "Cookie": "WordPress登录后的cookies（必需）",
                "User-Agent": "浏览器用户代理（推荐）",
                "Accept": "application/json, */*;q=0.1（推荐）",
                "Cache-Control": "no-cache（推荐）",
                "Pragma": "no-cache（推荐）",
                "Origin": "网站域名（推荐）",
                "Sec-Fetch-Dest": "empty（推荐）",
                "Sec-Fetch-Mode": "cors（推荐）",
                "Sec-Fetch-Site": "same-origin（推荐）"
            },
            "endpoints": {
                "posts": f"{self.api_url}/posts",
                "media": f"{self.api_url}/media",
                "categories": f"{self.api_url}/categories",
                "tags": f"{self.api_url}/tags",
                "users/me": f"{self.api_url}/users/me",
                "post_edit": f"{self.admin_url}/post.php",
                "ajax": self.ajax_url
            },
            "authentication_method": "基于Cookie的身份验证，某些操作可能需要nonce",
            "nonce_source": "从WordPress管理页面提取的wpApiSettings.nonce值",
            "notes": [
                "WordPress REST API默认需要身份验证才能执行写操作",
                "nonce是一种一次性使用的安全令牌，用于防止CSRF攻击",
                "如果cookie有效，某些简单操作可能不需要nonce",
                "更新文章后需要发送额外的表单数据来更新元数据",
                "请求之间添加了延迟，避免过于频繁的请求",
                "新增了特色图片设置功能，支持上传图片并设置为文章特色图片",
                "新增了分类和标签的获取与创建功能",
                "新增了一体化发布方法，支持同时上传图片和发布文章"
            ]
        }
        
        return api_info

    def upload_media(self, file_path, filename=None, use_nonce=True):
        """
        上传媒体文件（如图片）
        :param file_path: 本地文件路径
        :param filename: 自定义文件名（可选）
        :param use_nonce: 是否使用nonce验证
        :return: 上传结果，包含媒体ID等信息
        """
        try:
            print("开始上传媒体...")
            
            print("1. 添加请求延迟...")
            self._wait_between_requests()
            
            print("2. 检查文件是否存在...")
            # 检查文件是否存在
            if not os.path.exists(file_path):
                return {'success': False, 'error': f'文件不存在: {file_path}'}
            
            print(f"   文件存在: {file_path}")
            
            # 如果没有指定文件名，使用原文件名
            if not filename:
                filename = os.path.basename(file_path)
            
            print(f"3. 准备请求头，文件名: {filename}")
            
            headers = {}
            
            # 如果使用nonce，尝试获取
            if use_nonce:
                print("4. 获取nonce...")
                nonce = self.extract_nonce_from_page()
                if nonce:
                    headers['X-WP-Nonce'] = nonce
                    print(f"   Nonce: {nonce}")
                else:
                    print("   无法获取nonce")
            
            print("5. 准备文件数据...")
            # 准备文件
            with open(file_path, 'rb') as f:
                file_content = f.read()
            
            print(f"   文件大小: {len(file_content)} 字节")
            
            print("6. 发送POST请求...")
            # 准备files字典
            files = {'file': (filename, file_content, 'image/jpeg')}
            
            # 发送请求
            response = self.session.post(
                f"{self.api_url}/media?_locale=user",
                headers=headers,
                files=files
            )
            
            print(f"   响应状态码: {response.status_code}")
            
            if response.status_code == 201:
                result = response.json()
                print(f"媒体上传成功，ID: {result.get('id')}")
                return {'success': True, 'media_id': result.get('id'), 'data': result}
            else:
                print(f"媒体上传失败，状态码: {response.status_code}")
                
                # 尝试解析JSON响应并解码Unicode字符
                try:
                    error_data = response.json()
                    if 'message' in error_data:
                        # 解码Unicode编码的中文字符
                        message = error_data['message']
                        if '\\u' in message:
                            message = message.encode('utf-8').decode('unicode_escape')
                        print(f"错误信息: {message}")
                    else:
                        print(f"响应内容: {response.text}")
                except:
                    print(f"响应内容: {response.text}")
                
                # 如果使用nonce失败，尝试不使用nonce
                if use_nonce and response.status_code == 401:
                    print("尝试不使用nonce重新上传...")
                    return self.upload_media(file_path, filename, use_nonce=False)
                
                return {'success': False, 'error': response.text}
                
        except Exception as e:
            print(f"上传媒体时出错: {e}")
            import traceback
            traceback.print_exc()
            return {'success': False, 'error': str(e)}

    def _retry_request(self, func, *args, **kwargs):
        """
        带有重试机制的请求方法
        :param func: 要执行的函数
        :param args: 函数参数
        :param kwargs: 函数关键字参数
        :return: 函数执行结果
        """
        last_error = None
        
        for attempt in range(self.max_retries):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                last_error = e
                print(f"请求失败 (尝试 {attempt + 1}/{self.max_retries}): {str(e)}")
                
                if attempt < self.max_retries - 1:
                    # 计算延迟时间，使用指数退避
                    delay = self.retry_delay * (2 ** attempt) + random.uniform(0, 1)
                    print(f"等待 {delay:.2f} 秒后重试...")
                    time.sleep(delay)
        
        # 所有重试都失败了
        raise last_error
    
    def _make_request_with_retry(self, method, url, **kwargs):
        """
        发送带有重试机制的HTTP请求
        :param method: HTTP方法 ('GET', 'POST', etc.)
        :param url: 请求URL
        :param kwargs: 请求参数
        :return: 响应对象
        """
        # 设置超时
        if 'timeout' not in kwargs:
            kwargs['timeout'] = self.request_timeout
            
        # 执行请求
        response = self._retry_request(
            getattr(self.session, method.lower()),
            url, **kwargs
        )
        
        return response
    
    def _handle_response(self, response, success_codes=None, error_message=None):
        """
        处理HTTP响应
        :param response: 响应对象
        :param success_codes: 成功状态码列表，默认为[200, 201]
        :param error_message: 自定义错误消息
        :return: 处理结果
        """
        if success_codes is None:
            success_codes = [200, 201]
            
        if response.status_code in success_codes:
            return {'success': True, 'data': response.json() if response.text else {}}
        else:
            error_msg = error_message or f"请求失败，状态码: {response.status_code}"
            print(f"{error_msg}")
            print(f"响应内容: {response.text}")
            return {'success': False, 'error': response.text, 'status_code': response.status_code}

if __name__ == "__main__":
        publisher = WordPressPublisher()
